import StrategyFactory from './strategy'
import ParameterValidator from './funcSolve/parameterValidator'
import type { WetAirStateParameter } from './types'

// function memoize<T extends (...args: number[]) => number>(fn: T): T {
//   const cache = new Map()
//   return ((...args: number[]) => {
//     const key = JSON.stringify(args)
//     if (cache.has(key)) return cache.get(key)
//     const result = fn(...args)
//     cache.set(key, result)
//     return result
//   }) as T
// }

class Psychro {
  //#region 静态属性
  /**
   * 小数精度
   */
  static readonly PRECISION = 1e-8
  static readonly Ra = 287.042 // J/(kg·K)
  static readonly Rv = 461.524 // J/(kg·K)
  private static readonly PRECALCULATED_CONSTANTS = {
    // 预计算常用温度下的饱和蒸气压
    PVS_CACHE: new Map<string, number>(),
    /**
     * #### 预计算常用温度下的焓值系数
     */
    ENTHALPY_COEFFICIENTS: {
      /**
       * 干空气的定压比热，单位KJ/(kg·℃)
       */
      CP_AIR: 1.006,
      /**
       * 水蒸气的定压比热，单位KJ/(kg·℃)
       */
      CP_VAPOR: 1.86,
      /**
       * 每千克0℃的水变成0℃的水蒸气所需要的汽化潜热，单位KJ/kg
       */
      LATENT_HEAT: 2501
    }
  }
  /**
   * #### 比湿常数，单位Kg/kg
   * ‌18.015268‌是水的相对分子质量;
   * 28.966‌是空气的相对分子质量;
   * 18.015268/28.966=0.622
   */
  static readonly EPSILON = 0.621945 // 比湿常数

  /**
   * 开氏温度转摄氏度
   */
  static readonly KELVIN = 273.15

  /**
   * ### 湿空气密度，单位kg/m3
   */
  static readonly RHO_AIR = 1.2

  /**
   * #### 大气压力，单位KPa
   */
  static readonly ATMOSPHERIC_PRESSURE = 101.325
  //#endregion

  //#region 静态方法
  /**
   * #### 将摄氏温度转换为开尔文温度
   * @param temperature 摄氏温度值
   * @returns 返回开尔文温度值，单位K
   */
  static convertKelvin(temperature: number) {
    // 静态方法，用于温度转换
    return temperature + Psychro.KELVIN
  }

  /**
   * #### 将摄氏温度转换为华氏温度
   * @param temperature  摄氏温度，单位℃
   * @returns 返回华氏温度，单位℉
   */
  static convertFahrenheit(temperature: number) {
    return ((temperature - 32) * 5) / 9
  }

  /**
   * #### 计算湿空气密度的静态方法
   * @param temperature 温度，单位℃
   * @param pv 水蒸气分压力，单位KPa
   * @param p 大气压力，单位KPa
   * @returns 湿空气密度，单位Kg/m3
   */
  static calcDensity(
    temperature: number,
    pv = 2.985,
    p = Psychro.ATMOSPHERIC_PRESSURE
  ) {
    const t = Psychro.convertKelvin(temperature)
    return ((0.003484 * p) / t - (0.00134 * pv) / t) * 1000
  }

  /**
   * 计算湿空气的单位比容量的静态方法，单位：m3/Kga
   * @param dryTemperature 干球温度，单位：℃
   * @param wetTemperature 湿球温度，单位：℃
   * @param pressure 大气压力，单位：kPa
   * @returns
   */
  static calcSpecificVolume(
    dryTemperature: number,
    wetTemperature: number,
    pressure: number
  ) {
    const t = Psychro.convertKelvin(dryTemperature)
    const d = Psychro.calcHumidityFromDryWetTemperature(
      dryTemperature,
      wetTemperature,
      pressure
    )
    return (0.287042 * t * (1 + 1.607858 * d)) / pressure
  }

  /**
   * #### 计算饱和蒸气压(Pvs)的静态方法,带缓存
   * 根据1963年第六届国际水蒸气会议的决定，以水物质在三相（纯水的冰、水和汽）平衡共存状态下（t=0.01℃，p=0.6112 kPa）的饱和水作为基准点，规定在三相态时饱和水的内能和熵为零。
   * ASHRAE. 2013 ASHRAE Handbook Fundamentals SI Edition [M].
   * @param temperature 温度值，单位为摄氏度
   * @returns 返回饱和水蒸气压，单位为kPa
   */
  static calcPvs(temperature: number) {
    const easyT = temperature.toFixed(6)
    if (Psychro.PRECALCULATED_CONSTANTS.PVS_CACHE.has(easyT)) {
      return Psychro.PRECALCULATED_CONSTANTS.PVS_CACHE.get(easyT) as number
    }
    // 将摄氏温度转换为开尔文温度
    const t = Psychro.convertKelvin(temperature)

    // 使用预分配的常量数组
    const constants =
      temperature < 0.01
        ? [
            -5.6745359e3, 6.3925247, -9.677843e-3, 6.2215701e-7, 2.0747825e-9,
            -9.484024e-13, 4.1635019
          ]
        : [
            -5.8002206e3, 1.3914993, -4.8640239e-2, 4.1764768e-5, -1.4452093e-8,
            0, 6.5459673
          ]

    const result =
      Math.exp(
        constants[0] / t +
          constants[1] +
          constants[2] * t +
          constants[3] * t * t +
          constants[4] * t * t * t +
          constants[5] * t * t * t * t +
          constants[6] * Math.log(t)
      ) * 0.001
    Psychro.PRECALCULATED_CONSTANTS.PVS_CACHE.set(easyT, result)

    return result
  }

  /**
   * #### 通过干、湿球温度和大气压力计算水蒸气分压力的静态方法
   * @param dryTemperature 干球温度，单位℃
   * @param wetTemperature 湿球温度，单位℃
   * @param pressure 大气压力，单位KPa
   * @returns 水蒸气分压力，单位KPa
   */
  static calcPv(
    dryTemperature: number,
    wetTemperature: number,
    pressure: number
  ) {
    const pvs = Psychro.calcPvs(wetTemperature)
    const ds = Psychro.calcHumidity(pvs, pressure)
    const d =
      wetTemperature > 0
        ? ((2501 - 2.326 * wetTemperature) * ds -
            1.006 * (dryTemperature - wetTemperature)) /
          (2501 + 1.86 * dryTemperature - 4.186 * wetTemperature)
        : ((2830 - 0.24 * wetTemperature) * ds -
            1.006 * (dryTemperature - wetTemperature)) /
          (2830 + 1.86 * dryTemperature - 2.1 * wetTemperature)

    return pressure * (d / (d + this.EPSILON))
  }

  /**
   * 通过含湿量计算水蒸气分压力，，衍生出的函数；
   * @param humidity 含湿量，单位：Kg/Kg
   * @param pressure 大气压力，单位KPa
   * @returns 水蒸气分压力，单位KPa
   */
  static derivePv(humidity: number, pressure: number) {
    return (humidity * pressure) / (Psychro.EPSILON + humidity)
  }

  /**
   * #### 湿空气焓值计算
   * 公式：h = 1.006 * t + (1.86 * d * t + 2501 * d)
   * @param temperature 干球温度，单位℃
   * @param humidity 含湿量，单位g/kg
   * @returns 单位kJ/kg
   */
  static calcEnthalpy(temperature: number, humidity: number) {
    const { CP_AIR, CP_VAPOR, LATENT_HEAT } =
      Psychro.PRECALCULATED_CONSTANTS.ENTHALPY_COEFFICIENTS
    return (
      CP_AIR * temperature +
      (CP_VAPOR * humidity * temperature + LATENT_HEAT * humidity)
    )
  }

  /**
   * #### 通过焓值和含湿量计算干球温度，衍生出的函数；
   * @param enthalpy 焓值，单位kJ/kg
   * @param humidity 含湿量，单位Kg/kg
   * @returns
   */
  static deriveDryTemp(enthalpy: number, humidity: number) {
    const { CP_AIR, CP_VAPOR, LATENT_HEAT } =
      Psychro.PRECALCULATED_CONSTANTS.ENTHALPY_COEFFICIENTS
    return (enthalpy - humidity * LATENT_HEAT) / (CP_AIR + CP_VAPOR * humidity)
  }

  /**
   * #### 通过焓值和干球温度计算含湿量，衍生出的函数；
   * @param enthalpy 焓值，单位kJ/kg
   * @param dryTemperature 干球温度，单位℃
   * @returns 含湿量，单位Kg/kg
   */
  static deriveHumidity(enthalpy: number, dryTemperature: number) {
    const { CP_AIR, CP_VAPOR, LATENT_HEAT } =
      Psychro.PRECALCULATED_CONSTANTS.ENTHALPY_COEFFICIENTS
    return (
      (enthalpy - CP_AIR * dryTemperature) /
      (LATENT_HEAT + CP_VAPOR * dryTemperature)
    )
  }

  /**
   * #### 计算湿空气的含湿量
   * 公式：d = (0.621945 * pv) / (p - pv)
   * @param pv 为湿空气中的水蒸气分压力，单位KPa
   * @param p 大气压力，单位KPa
   * @returns  含湿量，单位kg/kg
   */
  static calcHumidity(pv: number, pressure: number) {
    return (Psychro.EPSILON * pv) / (pressure - pv)
  }

  static calcHumidityFromDryWetTemperature(
    dryTemperature: number,
    wetTemperature: number,
    pressure: number
  ) {
    const pv = Psychro.calcPv(dryTemperature, wetTemperature, pressure)
    return Psychro.calcHumidity(pv, pressure)
  }

  static resolveWetTemperatureForEnthalpy(enthalpy: number, pressure: number) {
    let tHigh = 100,
      tLow = -100,
      tw = 0
    const acc = 0.0005
    for (let j = 1; j < 1000; j++) {
      const dt = (tHigh - tLow) / 2
      tw = tLow + dt
      const pvs = Psychro.calcPvs(tw)
      const ds = Psychro.calcHumidity(pvs, pressure)
      const hMid = Psychro.calcEnthalpy(tw, ds)
      if (enthalpy - hMid > 0.0) {
        tLow = tw
      } else {
        tHigh = tw
      }
      if (Math.abs(dt) < acc) {
        break
      }
    }
    return tw
  }

  static calcRh(pv: number, pvs: number) {
    return (100 * pv) / pvs
  }

  /**
   * #### 计算相对湿度的静态方法
   * @param dryTemperature 干球温度，单位：℃
   * @param wetTemperature 湿球温度，单位：℃
   * @param pressure 大气压力，单位：kPa
   * @returns 相对湿度，单位：%
   */
  static calcRelativeHumidity(
    dryTemperature: number,
    wetTemperature: number,
    pressure: number
  ) {
    if (dryTemperature != wetTemperature) {
      const pv = Psychro.calcPv(dryTemperature, wetTemperature, pressure)
      const pvs = Psychro.calcPvs(dryTemperature)
      return Psychro.calcRh(pv, pvs)
    }
    return 100
  }

  /**
   * 计算露点温度的静态方法
   * @param pv - 大气中的水汽压(单位: kPa)
   * @returns 返回计算得到的露点温度(单位: °C)
   */
  static calcDewTemperature(pv: number) {
    // 计算水汽压的自然对数
    const y = Math.log(pv),
      yy = y * y,
      yyy = yy * y,
      powPv = Math.pow(pv, 0.1984)
    // 判断水汽压是否大于0.622082 kPa
    if (pv > 0.622082) {
      // 当水汽压大于0.622082 kPa时，使用第一种计算公式
      return (
        6.54 +
        14.526 * y + // y的线性项系数
        0.7389 * yy + // y的二次项系数
        0.09486 * yyy + // y的三次项系数
        0.4569 * powPv // 水汽压的指数项
      )
    }
    // 当水汽压小于等于0.622082 kPa时，使用第二种计算公式
    return 6.09 + 12.608 * y + 0.4959 * y * y
  }
  //#endregion

  //#region 属性
  readonly pressure: number
  /**
   * #### 干球温度，单位：℃
   */
  dryTemperature: number | undefined
  /**
   * #### 湿球温度，单位：℃
   */
  wetTemperature: number | undefined
  /**
   * #### 露点温度，单位：℃
   */
  dewTemperature: number | undefined
  /**
   * #### 比焓，单位：kJ/kg
   */
  enthalpy: number | undefined
  /**
   * #### 含湿量，单位：kg/kg
   */
  humidity: number | undefined
  /**
   * #### 相对湿度，单位：%
   */
  relativeHumidity: number | undefined
  //#endregion

  /**
   * Psychro 构造函数，用于计算湿空气的各种参数
   * @param wetAirStateParameter 一对状态参数;譬如：{t,d},但d、td与pv,还有h与tw不能成对；
   * @param pressure 大气压力，单位KPa
   */
  constructor(
    wetAirStateParameter: WetAirStateParameter,
    pressure = Psychro.ATMOSPHERIC_PRESSURE
  ) {
    ParameterValidator.validate(wetAirStateParameter, pressure)
    this.pressure = pressure

    const strategy = StrategyFactory.getStrategy(wetAirStateParameter)
    const result = strategy.calculate(wetAirStateParameter, pressure)
    Object.assign(this, result)
  }

  //#region 计算属性
  /**
   * #### 单位容积量，单位：m³/kg
   */
  get specificVolume() {
    if (this.dryTemperature == void 0 || this.wetTemperature == void 0) {
      return NaN
    }
    return Psychro.calcSpecificVolume(
      this.dryTemperature,
      this.wetTemperature,
      this.pressure
    )
  }

  /**
   * #### 湿空气密度，单位：kg/m3
   */
  get density() {
    if (this.dryTemperature == void 0) {
      return NaN
    }
    return Psychro.calcDensity(
      this.dryTemperature,
      this.vaporPressure,
      this.pressure
    )
  }
  /**
   * #### 水蒸气分压力，单位：KPa
   */
  get vaporPressure() {
    if (this.dryTemperature == void 0 || this.wetTemperature == void 0) {
      return NaN
    }
    return Psychro.calcPv(
      this.dryTemperature,
      this.wetTemperature,
      this.pressure
    )
  }

  /**
   * #### 当前干球温度下饱和水蒸气分压力，单位：KPa
   */
  get saturatedVaporPressure() {
    if (this.dryTemperature == void 0) {
      return NaN
    }
    return Psychro.calcPvs(this.dryTemperature)
  }

  //#endregion
}
export default Psychro
